﻿// Script Dependendies:
//  1. jquery.form plugin

using System.Text;

namespace BigfootMVC.Helpers
{

    /// <summary>
    /// Creates the script necessary to Ajaxify an html form. It wraps the jquery.form
    /// </summary>
    public class AjaxForm
    {

        private string _formName = "";
        private string _url = "";
        private bool _validate = true;
        private string _validateFunction = "";
        private string _type = "POST";
        // xml || json || script || null (default)
        private string _dataType = "";
        // element to be udpated
        private string _target = "";
        // Determine weather to replace the contents of the target or the whole target element
        private bool _replaceTarget;
        private string _beforeSerialize = "";
        private string _beforeSubmit = "";
        private string _success = "";
        // default = null
        private bool _resetForm;
        // default = null
        private bool _clearForm;
        private bool _wrapOnReady;
        private JSObject _otherOptions = new JSObject();

        private bool _wrapScriptTag;
        public AjaxForm()
        {
            _clearForm = false;
        }

        /// <summary>
        /// Factory method to ajaxify the form
        /// </summary>
        /// <param name="formName">The name of the form</param>
        public static AjaxForm Create(string formName)
        {
            return new AjaxForm
            {
                _formName = formName
            };
        }

        /// <summary>
        /// Factory method to ajaxify the form
        /// </summary>
        /// <param name="formName">The name of the form</param>
        /// <param name="url">The url the form will be posted to</param>
        public static AjaxForm Create(string formName, string url)
        {
            return new AjaxForm
            {
                _formName = formName,
                _url = url
            };
        }

        /// <summary>
        /// Cancels any validation from happening when submitting the form
        /// </summary>
        public AjaxForm DontValidate()
        {
            _validate = false;
            return this;
        }

        /// <summary>
        /// Specifies the function to call to determine whether the form is valid or not.
        /// </summary>
        /// <param name="functionName">The name of the function without the () at the end</param>
        /// <returns></returns>
        public AjaxForm ValidateFunction(string functionName)
        {
            functionName = functionName.Trim().EndsWith("()") ? functionName : functionName + "()";
            _validateFunction = functionName;
            return this;
        }

        public AjaxForm POST()
        {
            return RequestType("POST");
        }

        public AjaxForm POST(string postUrl)
        {
            RequestType("POST");
            _url = postUrl;
            return this;
        }

        public AjaxForm GET()
        {
            return RequestType("GET");
        }

        public AjaxForm GET(string getUrl)
        {
            RequestType("GET");
            _url = getUrl;
            return this;
        }

        /// <summary>
        /// Sets the ajax request to be a GET or POST
        /// </summary>
        /// <param name="value">GET or POST</param>
        public AjaxForm RequestType(string value)
        {
            _type = value;
            return this;
        }

        /// <summary>
        /// Specifies the dataType being returned from the ajax request
        /// </summary>
        /// <param name="value">xml || json || script || null (which is the default signals it that is html)</param>
        public AjaxForm DataType(string value)
        {
            _dataType = value;
            return this;
        }

        /// <summary>
        /// Specifies the datatype being returned from the ajax request as bing json
        /// </summary>
        public AjaxForm json()
        {
            return DataType("json");
        }

        /// <summary>
        /// Replaces the content of this element id with the data returned from the request
        /// </summary>
        public AjaxForm Target(string elementId)
        {
            _target = elementId;
            return this;
        }

        /// <summary>
        /// Replaces the content of this element id with the data returned from the request. 
        /// When replace target = false, then it appends the data instead of replacing it which is the default behaviour
        /// </summary>
        public AjaxForm Target(string elementId, bool replaceTarget)
        {
            _target = elementId;
            _replaceTarget = replaceTarget;
            return this;
        }

        /// <summary>
        /// Calls this function or javascript before the form is serialized. Allows you to do some preprocessing on the form
        /// </summary>
        /// <param name="script">function() or actual javascript to run like function(){ statements; }</param>
        public AjaxForm BeforeSerialize(string script)
        {
            _beforeSerialize = script;
            return this;
        }

        /// <summary>
        /// Calls this function or javascript before the form is submitted. Allows you to do some preprocessing on the form before it is submitted
        /// </summary>
        /// <param name="script">function() or actual javascript to run like function(){ statements; }</param>
        public AjaxForm BeforeSubmit(string script)
        {
            _beforeSubmit = script;
            return this;
        }

        /// <summary>
        /// Calls this function after the ajax request completes successfully
        /// </summary>
        /// <param name="script">function() or actual javascript to run like function(){ statements; }</param>
        public AjaxForm Success(string script)
        {
            _success = script;
            return this;
        }

        /// <summary>
        /// Calls this function fter the ajax request completes with a status code other than 200
        /// </summary>
        /// <param name="script">Function name or anonymous function(){ statements; }</param>
        public AjaxForm Error(string script)
        {
            _otherOptions.Add("error", script);
            return this;
        }

        /// <summary>
        /// Redirects to the specified url after the ajax request completes successfully
        /// </summary>
        /// <param name="url">The url you wish to redirect to</param>
        public AjaxForm Success_Redirect(string url)
        {
            _success = "function() { window.location = '" + url + "'; }";
            return this;
        }

        /// <summary>
        /// Clears the form on ajax complete
        /// </summary>
        public AjaxForm ClearForm()
        {
            _clearForm = true;
            return this;
        }

        /// <summary>
        /// Resets the form on ajax complete
        /// </summary>
        public AjaxForm ResetForm()
        {
            _resetForm = true;
            return this;
        }

        /// <summary>
        /// Wraps the script created with this interface in a jQuery onready event to be outputed to the page
        /// </summary>
        public AjaxForm OnReady()
        {
            return OnReady(false);
        }

        /// <summary>
        /// Wraps the script created with this interface in a jQuery onready event to be outputed to the page, and it determines weather to
        /// add the script tag to the output once you call ToString
        /// </summary>
        public AjaxForm OnReady(bool addScriptTag)
        {
            _wrapOnReady = true;
            _wrapScriptTag = addScriptTag;
            return this;
        }

        /// <summary>
        /// Wraps in a script tag as well as in a jQuery on ready event
        /// </summary>
        public AjaxForm AddScriptTag()
        {
            return AddScriptTag(true);
        }

        /// <summary>
        /// Wraps in a script tag. If addJQueryOnReady is true then it also wraps it in a jQuery on ready event
        /// </summary>
        public AjaxForm AddScriptTag(bool addJQueryOnReady)
        {
            _wrapScriptTag = true;
            return OnReady(addJQueryOnReady);            
        }

        /// <summary>
        /// Grabs all the values and options specified and creates the proper script to ouput to the page. If you called ton OnReady it outputs the script
        /// within a jQuery on document ready function, if you flagged it to AddScriptTag it sorrounds the whole script with the propery <script></script> tag
        /// </summary>
        /// <returns>Properly formatted javascript to include in the page</returns>
        public string ToString(bool addScriptTag)
        {
            return AddScriptTag().ToString();
        }

        /// <summary>
        /// Grabs all the values and options specified and creates the proper script to ouput to the page. If you called ton OnReady it outputs the script
        /// within a jQuery on document ready function, if you flagged it to AddScriptTag it sorrounds the whole script with the propery <script></script> tag
        /// </summary>
        /// <returns>Properly formatted javascript to include in the page</returns>
        public override string ToString()
        {
            var options = new JSObject();
            options.AddString("url", _url);
            options.AddString("type", _type);
            options.Merge(_otherOptions);
            if (!string.IsNullOrEmpty(_dataType))
            {
                options.AddString("dataType", _dataType);
            }
            if (!string.IsNullOrEmpty(_target))
            {
                options.AddString("target", _target);
                if (_replaceTarget)
                {
                    options.Add("replaceTarget", "true");
                }
            }

            // If the user does not specify a validation function, then assign a default
            if (string.IsNullOrEmpty(_validateFunction)) 
                _validateFunction = "jQuery('#" + _formName + "').validate().form()";
            
            if (!string.IsNullOrEmpty(_beforeSerialize) & _validate == false)
            {
                options.Add("beforeSerialize", _beforeSerialize);
            }
            else if (!string.IsNullOrEmpty(_beforeSerialize) & _validate)
            {
                options.Add("beforeSerialize", "function(){ " + _beforeSerialize + "(); if (!" + _validateFunction + ") return false; }");
            }
            else if (_validate)
            {
                options.Add("beforeSerialize", "function() { if (!" + _validateFunction + ") return false; }");
            }
            if (!string.IsNullOrEmpty(_beforeSubmit))
            {
                options.Add("beforeSubmit", _beforeSubmit);
            }
            if (!string.IsNullOrEmpty(_success) || options.HasMember("error"))
            {
                // Need to check that the response is actually a success before executing it
                var ssb = new StringBuilder();
                ssb.Append("function(responseText, statusText, xhr, $form) { ");

                // Sometimes the response is 0 or undefined but there is not an error in the response 
                //   (this happens sometimes when uploading a form with a file input)
                ssb.Append("    var undefinedWithError = false; ");
                ssb.Append("    try {");
                ssb.Append("         if ((xhr.status == undefined || xhr.status == 0) && responseText.indexOf('<span><h1>Server Error in') != -1) { ");
                ssb.Append("              undefinedWithError = true;");
                ssb.Append("         }");
                ssb.Append("    } catch(e){log('###ERROR: ' + e);}");

                //ssb.Append("    alert('statusIs0OrUndefined: ' + (xhr.status == undefined || xhr.status == 0));")
                //ssb.Append("    alert('statusText: ' + statusText + ' \n status: ' + xhr.status + '\n undefinedWithError: ' + undefinedWithError + '\n\n' + responseText);")
                //ssb.Append("if (undefinedWithError) alert('***ERROR***: StatusText: ' + statusText + ' status: ' + xhr.status);")

                // Trigger an error handler if any
                if (options.HasMember("error"))
                    ssb.Append("if (undefinedWithError)(" + options.GetValue("error") + ")(responseText, statusText, xhr, $form);");
                
                // Call success only after checking that there were no undefined with errors
                if (!string.IsNullOrEmpty(_success))
                    ssb.Append("if (!undefinedWithError) (" + _success + ")(responseText, statusText, xhr, $form);");
                
                ssb.Append("}");
                options.Add("success", ssb.ToString());
            }

            if (_resetForm)
            {
                options.Add("resetForm", "true");
            }
            if (_clearForm)
            {
                options.Add("clearForm", "true");
            }

            var script = "";

            // Add the script tag if requested
            if (_wrapScriptTag)
                script += "<script type='text/javascript'>";
            // Add the jquery on ready tag
            if (_wrapOnReady)
                script += "jQuery(function(){";
            script += "jQuery('#" + _formName + "').ajaxForm(" + options + ");";
            // Close the jquery on ready tag
            if (_wrapOnReady)
                script += "});";
            // Close the script onready tag
            if (_wrapScriptTag)
                script += "</script>";

            return script;
        }

    }

}
